namespace Sudoku.SourceGeneration.Handlers;

internal static class AddPropertyHandler
{
	public static void Output(SourceProductionContext spc, ImmutableArray<CollectedResult> values)
	{
		var result = new List<string>();
		foreach (var typeGroup in values.GroupBy(static r => r.TypeSymbol))
		{
			var typeSymbol = typeGroup.Key;
			var methodDeclarations = string.Join(
				"\r\n\r\n",
				from collectedResult in typeGroup select collectedResult.MethodDeclarationCodeSnippet
			);

			var namespaceString = typeSymbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
			result.Add(
				$$"""
				namespace {{namespaceString["global::".Length..]}}
				{
					/// <summary>
					/// Provides with a list of methods that are used as factory methods on modifying properties with multiple method invocations:
					/// <code><![CDATA[
					/// var instance = new Instance()
					///     .WithBaz(1, 3, 2, 4, 5)
					///     .AddFoo(42)
					///     .AddBar(string.Empty);
					/// ]]></code>
					/// </summary>
					[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
					[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{typeof(AddPropertyHandler).FullName}}", "{{Value}}")]
					[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					public static partial class __{{typeSymbol.Name}}FactoryMethods_Add
					{
				{{string.Join("\r\n\r\n\t\t", methodDeclarations)}}
					}
				}
				"""
			);
		}

		spc.AddSource(
			"AddProperty.g.cs",
			$$"""
			{{Banner.AutoGenerated}}
			
			#nullable enable

			{{string.Join("\r\n\r\n", result)}}
			"""
		);
	}

	public static CollectedResult? Transform(GeneratorAttributeSyntaxContext gasc, CancellationToken cancellationToken)
	{
		if (gasc is not
			{
				TargetSymbol: IPropertySymbol
				{
					ContainingType: { TypeKind: var typeKind, TypeParameters: var typeParameters } type,
					DeclaredAccessibility: var accessibility,
					Name: var propertyName,
					Type: var propertyType,
					IsIndexer: false,
					IsReadOnly: true
				},
				Attributes: [{ NamedArguments: var namedArguments }],
				SemanticModel.Compilation: var compilation
			})
		{
			return null;
		}

		var spanTypeSymbol = compilation.GetTypeByMetadataName("System.ReadOnlySpan`1")!;
		var typeParametersRawString = string.Join(", ", from tp in typeParameters select tp.ToDisplayString());
		var typeParametersString = typeParameters.Length == 0 ? string.Empty : $"<{typeParametersRawString}>";
		var typeString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
		var typeModifier = typeKind == TypeKind.Struct ? "ref " : string.Empty;
		var parameterModifiersString = namedArguments.TryGetValueOrDefault<string>("ParameterModifiers", out var m) ? $"{m} " : string.Empty;
		var parameterNameString = namedArguments.TryGetValueOrDefault<string>("ParameterName", out var p)
			? p
			: propertyName.ToCamelCasing();
		var propertyElementType = (namedArguments.TryGetValueOrDefault<ITypeSymbol>("ParameterType", out var pt) ? pt! : propertyType)
			.GetCollectionElementType();
		if (propertyElementType is null)
		{
			return null;
		}

		var propertyTypeString = propertyElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
		var propertyElementListType = (namedArguments.TryGetValueOrDefault<INamedTypeSymbol>("MultipleAddingPropertyType", out var mpt) ? mpt! : spanTypeSymbol)
			.Construct(propertyElementType)
			.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
		var methodSuffixNameString = namedArguments.TryGetValueOrDefault<string>("MethodSuffixName", out var ms)
			? ms
			: propertyName.ToPascalCasing();
		var accessibilityString = namedArguments.TryGetValueOrDefault<string>("Accessibility", out var a)
			? a
			: accessibility switch
			{
				DeclaredAccessibility.Private => "private ",
				DeclaredAccessibility.ProtectedAndInternal => "private protected ",
				DeclaredAccessibility.Protected => "protected ",
				DeclaredAccessibility.Internal => "internal ",
				DeclaredAccessibility.ProtectedOrInternal => "protected internal ",
				DeclaredAccessibility.Public => "public ",
				_ => string.Empty
			};
		var paramsKeyword = namedArguments.TryGetValueOrDefault<bool>("DisallowsMultipleAddingParamsModifier", out var apa) && apa
			? string.Empty
			: "params ";
		var multipleAddingMethod = namedArguments.TryGetValueOrDefault<bool>("AllowsMultipleAdding", out var ama) && ama
			? $$"""


					/// <summary>
					/// Appends an element into the property <see cref="{{typeString}}.{{propertyName}}"/>.
					/// </summary>
					/// <param name="instance">The instance to be set or updated.</param>
					/// <param name="{{parameterNameString}}">A list of values to be added.</param>
					/// <returns>The value same as <see cref="{{typeString}}"/>.</returns>
					[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{typeof(AddPropertyHandler).FullName}}", "{{Value}}")]
					[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					{{accessibilityString}}static {{typeModifier}}{{typeString}} Add{{methodSuffixNameString}}{{typeParametersString}}(this {{typeModifier}}{{typeString}} instance, {{paramsKeyword}}{{propertyElementListType}} {{parameterNameString}})
					{
						foreach (var element in {{parameterNameString}})
						{
							instance.{{propertyName}}.Add(element);
						}
						return instance;
					}
			"""
			: string.Empty;
		return new(
			type,
			$$"""
					/// <summary>
					/// Appends an element into the property <see cref="{{typeString}}.{{propertyName}}"/>.
					/// </summary>
					/// <param name="instance">The instance to be updated.</param>
					/// <param name="{{parameterNameString}}">The value to be added.</param>
					/// <returns>The value same as <see cref="{{typeString}}"/>.</returns>
					[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{typeof(AddPropertyHandler).FullName}}", "{{Value}}")]
					[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute]
					{{accessibilityString}}static {{typeModifier}}{{typeString}} Add{{methodSuffixNameString}}{{typeParametersString}}(this {{typeModifier}}{{typeString}} instance, {{parameterModifiersString}}{{propertyTypeString}} {{parameterNameString}})
					{
						instance.{{propertyName}}.Add({{parameterNameString}});
						return instance;
					}{{multipleAddingMethod}}
			"""
		);
	}


	public sealed record CollectedResult(INamedTypeSymbol TypeSymbol, string MethodDeclarationCodeSnippet);
}
